# załadowanie modelu
import pickle
model = pickle.load(open("../../../../WB-XAI-Projekt/RF_model", "rb"))
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
# Wczytanie i przygotowanie danych
full_data = pd.read_csv("hotel_bookings.csv")
full_data["agent"] = full_data["agent"].astype(str)
treshold = 0.005 * len(full_data)
agents_to_change = full_data['agent'].value_counts()[full_data['agent'].value_counts() < treshold].index
full_data.loc[full_data["agent"].isin(agents_to_change), "agent"] = "other"
countries_to_change = full_data['country'].value_counts()[full_data['country'].value_counts() < treshold].index
full_data.loc[full_data["country"].isin(countries_to_change), "country"] = "other"
# Określenie cech uwzględnionych w modelu
num_features = ["lead_time", "arrival_date_week_number",
"stays_in_weekend_nights", "stays_in_week_nights",
"adults", "previous_cancellations",
"previous_bookings_not_canceled",
"required_car_parking_spaces", "total_of_special_requests",
"adr", "booking_changes"]
cat_features = ["hotel", "market_segment", "country",
"reserved_room_type",
"customer_type", "agent"]
features = num_features + cat_features
# Podział na zmienne wyjaśniające i target
X = full_data.drop(["is_canceled"], axis=1)[features]
y = full_data["is_canceled"]
categorical_names = {}
for feature in cat_features:
col = X[[feature]]
cat_transformer = SimpleImputer(strategy="constant", fill_value="Unknown")
col = cat_transformer.fit_transform(col)
X[feature] = col
le = LabelEncoder()
le.fit(X[[feature]])
X[[feature]] = le.transform(X[[feature]])
categorical_names[feature] = le.classes_
categorical_names
# Preprocessing
num_transformer = SimpleImputer(strategy="constant")
preprocessor = ColumnTransformer(transformers=[("num", num_transformer, num_features)],
remainder = 'passthrough')
for feature in num_features:
X[feature] = X[feature].astype(float)
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, random_state=42)
selected_X = X_train.iloc[[13]]
selected_y = y_train.iloc[13]
predicted_y = model.predict(selected_X)
print("Prawdziwa wartość:", selected_y)
print("Przewidziana wartość:", predicted_y)
selected_X.head()
Nasz model po raz kolejny poprawnie przewidział wartość (mimo że został odrobinę zmieniony od poprzedniej pracy domowej).
import dalex as dx
explainer = dx.Explainer(model, X_train, y_train, label = "Random Forest")
ps = explainer.predict_surrogate(selected_X, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
def print_cat(selected_X):
print("country:", categorical_names["country"][selected_X["country"]])
print("customer_type:", categorical_names["customer_type"][selected_X["customer_type"]])
print("market_segment:", categorical_names["market_segment"][selected_X["market_segment"]])
print("agent:", categorical_names["agent"][selected_X["agent"]])
print_cat(selected_X)
Największy wpływ na uzyskaną wartość (czyli brak anulowania rezerwacji) miała informacja o braku wcześniejszych anulowań. To, liczba specjalnych żądań oraz agent miało decydujący wpływ na uzyskany wynik (wszystko inne skłaniało ku rezygnacji). Zaskoczyło mnie, jak bardzo brak wcześniejszej anulacji wpłynął na predykcję - w poprzednim zadaniu domowym (break down) dla tej obserwacji to była dość ważna, ale zdecydowanie nie najważniejsza zmienna. Co prawda zmieniliśmy odrobinę model, ale skala i tak robi wrażenie.
Do wykonania tej części użyjemy obserwacji z poprzedniej pracy domowej. (Jeszcze raz dodam, że zmieniliśmy model - zmniejszyliśmy liczbę zmiennych w modelu i ze względu na sposób działania LIME zmieniliśmy OneHotEncoding na LabelEncoding).
X1 = X_train.iloc[[2137]]
X2 = X_train.iloc[[420]]
ps = explainer.predict_surrogate(X1, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
print_cat(X1)
W przypadku tej obserwacji znowu ogromny wpływ na przewidzenie braku anulowania rezerwacji miał wpływ brak wcześniejszego anulowania (trudno napisać to tak, żeby brzmiało po polsku :)). Jedyne inne zmienne, które tu mają wpływ i są takie same dla obu obserwacji to required_car_parking_spaces, previous_bookings_not_canceled i booking_changes. W obu predykcjach zadziałały w tym samym kierunku (czyli w kierunku rezygnacji) i ze zbliżonymi wartościami (choć mimo wszystko nieco innymi).
Znowu previous_cancellation ma znacznie większy wpływ dla tej obserwacji niż w przypadku metody break down, ale jednak widać, że zmienne istotne dla break down (tj. customer_type, total_of_special_requests) tu też osiągają dość wysokie wartości (choć jak widać total_of_special_requests nie jest kluczowe, skoro prawdopodobieństwo braku anulowania rezerwacji wynosi 100%).
ps = explainer.predict_surrogate(X2, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
print_cat(X2)
W przypadku tej obserwacji znowu previous_cancellations jest kluczowe. Większość z pozostałych zmiennych już wystapiła przy poprzednich obserwacjach i znowu ma podobny wpływ. Ciekawe jest, że wśród tych 10 zmiennych aż 7 przyjęło taką samą wartość jak dla obserwacji pierwszej (tj.previous_cancellations, required_car_parking_spaces, customer_type, total_of_special_requests, market_segment, booking_changes, previous_bookings_not_canceled), a jednak prawdopodobieństwo braku anulacji jest znacznie wyższe - graniczące z pewnością (hehe, może to ze względu na kraj "inny", analogicznie jak w drugiej obserwacji ;)).
Spróbujemy jeszcze dodatkowo znaleźć choć jedną obserwację, dla której mamy predykcję anulowania rezerwacji.
X3 = X_train.iloc[[2]]
ps = explainer.predict_surrogate(X3, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
print_cat(X3)
Znowu previous_cancellations=0 zwiększa prawdopodobieństwo braku anulowania rezygnacji, ale już nie jest kluczowe. Ogólnie wartości dla tych samych zmiennych mają wpływ w tę samą stronę z podobną wartością, więc po porównaniu tych 4 obserwacji można uznać, że LIME jest dość stabilną metodą (przynajmniej w porównaniu do break down).
Ogólne wnioski:
print_cat, ale nie przeszkadza to w interpretacji wykresów)